package com.apobates.forum.trident.controller;

import java.io.File;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import com.apobates.forum.attention.core.ImagePathCoverter;
import com.apobates.forum.core.ad.ActiveDirectoryConnectorFactory;
import com.apobates.forum.core.api.ImageIOMeta;
import com.apobates.forum.core.api.TagRelateTopic;
import com.apobates.forum.core.api.TopicFileCache;
import com.apobates.forum.core.api.service.AlbumService;
import com.apobates.forum.core.api.service.BoardModeratorService;
import com.apobates.forum.core.api.service.BoardService;
import com.apobates.forum.core.api.service.BoardTopicCategoryIndexService;
import com.apobates.forum.core.api.service.PostsService;
import com.apobates.forum.core.api.service.TopicActionCollectionService;
import com.apobates.forum.core.api.service.TopicConfigService;
import com.apobates.forum.core.api.service.TopicService;
import com.apobates.forum.core.api.service.TopicStatsService;
import com.apobates.forum.core.api.service.TopicTagService;
import com.apobates.forum.core.entity.Album;
import com.apobates.forum.core.entity.Board;
import com.apobates.forum.core.entity.BoardTopicCategoryIndex;
import com.apobates.forum.core.entity.Posts;
import com.apobates.forum.core.entity.Topic;
import com.apobates.forum.core.entity.TopicConfig;
import com.apobates.forum.core.entity.TopicStats;
import com.apobates.forum.event.elderly.ActionEventCulpritor;
import com.apobates.forum.event.elderly.ForumActionEnum;
import com.apobates.forum.trident.OnlineDescriptor;
import com.apobates.forum.trident.exception.ForumValidateException;
import com.apobates.forum.trident.exception.ResourceNotFoundException;
import com.apobates.forum.trident.controller.form.TopicConfigForm;
import com.apobates.forum.trident.controller.form.TopicForm;
import com.apobates.forum.trident.controller.form.TopicMoveForm;
import com.apobates.forum.trident.controller.helper.ReportPlugTopic;
import com.apobates.forum.trident.controller.helper.TopicQRCodeFileCacheHandler;
import com.apobates.forum.trident.digest.ForumReplierDigest;
import com.apobates.forum.trident.digest.ForumThreadsDigest;
import com.apobates.forum.member.MemberBaseProfile;
import com.apobates.forum.member.MemberProfileBean;
import com.apobates.forum.member.api.service.MemberService;
import com.apobates.forum.member.entity.MemberGroupEnum;
import com.apobates.forum.member.entity.MemberRoleEnum;
import com.apobates.forum.member.storage.core.MemberSessionBean;
import com.apobates.forum.strategy.Strategy;
import com.apobates.forum.strategy.StrategyEntityParam;
import com.apobates.forum.strategy.StrategyMode;
import com.apobates.forum.strategy.exposure.impl.BoardDetectionStrategy;
import com.apobates.forum.utils.CommonLink;
import com.apobates.forum.utils.CommonLink.CommonLinkBuilder;
import com.apobates.forum.utils.Commons;
import com.apobates.forum.utils.TipMessage;
import com.apobates.forum.utils.lang.EnumArchitecture;
import com.apobates.forum.utils.persistence.Page;
import com.apobates.forum.utils.persistence.PageRequest;
import com.apobates.forum.utils.zxing.GeneralQRCode.ImageType;
import com.google.gson.Gson;
/**
 * 话题控制器
 * 
 * @author xiaofanku
 * @since 20190306
 */
@Controller
@RequestMapping(value = {"/topic", "/threads"})
public class TopicController {
	@Autowired
	private TopicService topicService;
	@Autowired
	private PostsService postsService;
	@Autowired
	private TopicConfigService topicConfigService;
	@Autowired
	private TopicActionCollectionService topicActionCollectionService;
	@Autowired
	private MemberService memberService;
	@Autowired
	private BoardService boardService;
	@Autowired
	private BoardModeratorService boardModeratorService;
	@Autowired
	private BoardTopicCategoryIndexService boardTopicCategoryIndexService;
	@Autowired
	private AlbumService albumService;
	@Autowired
	private TopicTagService topicTagService;
	@Autowired
	private TopicStatsService topicStatsService;
	@Autowired
	private ImageIOMeta imageIOConfig;
	@Autowired
	private ServletContext servletContext;
	@Value("${site.domain}")
	private String siteDomain;
	@Value("${site.pageSize}")
	private int pageSize;
	@Value("${site.sensitive.dictionary}")
	private String sensitiveDictionary;
	private final static Logger logger = LoggerFactory.getLogger(TopicController.class);
	
	@ModelAttribute("topicArg")
	public Topic getTopic(HttpServletRequest request){
		return ActiveDirectoryConnectorFactory.parseRefererToTopic(request.getHeader("referer"), siteDomain).orElse(null);
	}
	
	// 话题主页
	@GetMapping(path="/{path}.xhtml")
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_BROWSE)
	@Strategy(action=ForumActionEnum.TOPIC_BROWSE, param=StrategyEntityParam.URI, mode=StrategyMode.READ)
	public String topicHome(
			@PathVariable("path") String connectValue, 
			@RequestParam(value = "author", required = false, defaultValue = "0") long filterMemberId, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model) {
		Topic tpObj = ActiveDirectoryConnectorFactory.build(Topic.class, connectValue).orElseThrow(()->new ResourceNotFoundException("话题参数解析失败"));
		// 话题的统计怎么加载: A是要推(websocket)?用户真的关心这个数据的实时性吗!B是要拉(ajax)?
		Topic topic = topicService.getTopicContentAndStats(tpObj.getId(), (topicAuthorId)->memberService.get(topicAuthorId)); //有统计数据
		//----------------------------------策略检查开始,不限制角色
		//  迁至StrategyInterceptorAdapter
		//----------------------------------策略检查结束
		Board board = boardService.get(topic.getBoardId(), topic.getVolumesId());
		topic.setBoard(board);
		model.addAttribute("topic", topic);
		// 只看楼主的
		model.addAttribute("filterAuthor", filterMemberId);
		model.addAttribute("authorStyle", MemberBaseProfile.getStyle(topic.getContent().getMember())); //style方法从Member中删除了
		// 话题的标签
		model.addAttribute("tages", topicTagService.getAllBy(topic.getId()));
		// 话题下的回复异步加载
		return "default/topic/view";
	}

	// ------------------------------------------------------------------------------以下是话题的操作
	// 版主置顶话题
	@PostMapping(path = "/top", produces = "application/json;charset=UTF-8")
	@ResponseBody
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_TOP, isAjax=true)
	@Strategy(action=ForumActionEnum.TOPIC_TOP, allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
	public TipMessage topTopicAction(
			@RequestParam(value = "token", required = false, defaultValue = "0")String token, 
			@ModelAttribute("topicArg")Topic tpObj, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model) {
		//----------------------------------从Http Referer中获取需要的参数
		if(null==tpObj || tpObj.getId()<1){
			return TipMessage.ofError("操作参数解析失败");
		}
		long topicId = tpObj.getId(); 
		//----------------------------------
		ActionEventCulpritor aec = ActionEventCulpritor.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
		//----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以
		//  迁至StrategyInterceptorAdapter
		//----------------------------------策略检查结束
		return TipMessage.Builder.take(()->topicService.editTop(topicId, aec)).success("话题置顶成功").error("操作失败");
	}
	
	// 版主话题加精
	@PostMapping(path = "/best", produces = "application/json;charset=UTF-8")
	@ResponseBody
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_BEST, isAjax=true)
	@Strategy(action=ForumActionEnum.TOPIC_BEST, allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
	public TipMessage bestTopicAction(
			@RequestParam(value = "token", required = false, defaultValue = "0")String token, 
			@ModelAttribute("topicArg")Topic tpObj, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model) {
		//----------------------------------从Http Referer中获取需要的参数
		if(null==tpObj || tpObj.getId()<1){
			return TipMessage.ofError("操作参数解析失败");
		}
		long topicId = tpObj.getId(); 
		//----------------------------------
		ActionEventCulpritor aec = ActionEventCulpritor.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
		//----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以
		//  迁至StrategyInterceptorAdapter
		//----------------------------------策略检查结束
		return TipMessage.Builder.take(()->topicService.editGoods(topicId, aec)).success("话题加精成功").error("操作失败");
	}
	
	// 版主锁定话题
	@PostMapping(path = "/lock", produces = "application/json;charset=UTF-8")
	@ResponseBody
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_LOCK, isAjax=true)
	@Strategy(action=ForumActionEnum.TOPIC_LOCK, allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
	public TipMessage lockTopicAction(
			@RequestParam(value = "token", required = false, defaultValue = "0")String token, 
			@ModelAttribute("topicArg")Topic tpObj, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model) {
		//----------------------------------从Http Referer中获取需要的参数
		if(null==tpObj || tpObj.getId()<1){
			return TipMessage.ofError("操作参数解析失败");
		}
		long topicId = tpObj.getId(); 
		//----------------------------------
		ActionEventCulpritor aec = ActionEventCulpritor.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
		//----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以
		//  迁至StrategyInterceptorAdapter
		//----------------------------------策略检查结束
		return TipMessage.Builder.take(()->topicService.lock(topicId, aec)).success("话题成功锁定").error("操作失败");
	}
	
	// 版主解锁话题
	@PostMapping(path = "/unlock", produces = "application/json;charset=UTF-8")
	@ResponseBody
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_UNLOCK, isAjax=true)
	@Strategy(action=ForumActionEnum.TOPIC_UNLOCK, allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
	public TipMessage unlockTopicAction( 
			@RequestParam(value = "token", required = false, defaultValue = "0")String token, 
			@ModelAttribute("topicArg")Topic tpObj, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model) {
		//----------------------------------从Http Referer中获取需要的参数
		if(null==tpObj || tpObj.getId()<1){
			return TipMessage.ofError("操作参数解析失败");
		}
		long topicId = tpObj.getId(); 
		//----------------------------------
		ActionEventCulpritor aec = ActionEventCulpritor.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
		//----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以
		//  迁至StrategyInterceptorAdapter
		//----------------------------------策略检查结束
		return TipMessage.Builder.take(()->topicService.releaseLock(topicId, aec)).success("话题解锁成功").error("操作失败");
	}
	
	// 版主删除话题
	@PostMapping(path = "/delete", produces = "application/json;charset=UTF-8")
	@ResponseBody
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_DEL, isAjax=true)
	@Strategy(action=ForumActionEnum.TOPIC_DEL, allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
	public TipMessage deleteTopicAction(
			@RequestParam(value = "token", required = false, defaultValue = "0")String token, 
			@ModelAttribute("topicArg")Topic tpObj, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model) {
		//----------------------------------从Http Referer中获取需要的参数
		if(null==tpObj || tpObj.getId()<1){
			return TipMessage.ofError("操作参数解析失败");
		}
		long topicId = tpObj.getId(); 
		//----------------------------------
		ActionEventCulpritor aec = ActionEventCulpritor.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
		//----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以
		//  迁至StrategyInterceptorAdapter
		//----------------------------------策略检查结束
		return TipMessage.Builder.take(()->topicService.remove(topicId, aec)).success("话题成功删除").error("操作失败");
	}
	
	// 版主移动话题
	@GetMapping(path = "/move")
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_MOVE)
	@Strategy(action=ForumActionEnum.TOPIC_MOVE, allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
	public String moveTopicForm(
			@RequestParam(value = "token", required = false, defaultValue = "0")String token, 
			@ModelAttribute("topicArg")Topic tpObj, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model){
		//----------------------------------从Http Referer中获取需要的参数
		if(null==tpObj || tpObj.getId()<1){
			throw new ResourceNotFoundException("操作参数解析失败");
		}
		long topicId = tpObj.getId(); long boardId = tpObj.getBoardId(); int boardGroupId = tpObj.getVolumesId();
		//----------------------------------
		Topic topic = topicService.get(topicId, boardId, boardGroupId);
		//----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以
		//  迁至StrategyInterceptorAdapter
		//----------------------------------策略检查结束
		TopicMoveForm form = new TopicMoveForm();
		form.setTopicId(topic.getId()+"");
		form.setTopicTitle(topic.getTitle());
		form.setBoardId(topic.getBoardId()+"");
		form.setBoardTitle(topic.getBoard().getTitle());
		form.setVolumesId(topic.getVolumesId()+"");
		form.setToken(token); 
		model.addAttribute("form", form);
		return "default/topic/move";
	}
	@PostMapping(path = "/move")
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_MOVE)
	@Strategy(action=ForumActionEnum.TOPIC_MOVE, param=StrategyEntityParam.QUERY_STR, paramId="topicId", allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
	public String moveTopicAction(
			@ModelAttribute("form") TopicMoveForm form, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model) {
		ActionEventCulpritor aec = ActionEventCulpritor.getInstance(mbean.getMid(), mbean.getNickname(), request, form.getToken());
		//----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以
		//  迁至StrategyInterceptorAdapter
		//----------------------------------策略检查结束
		logger.info("[MTV][0]话题: "+form.getLongTopicId()+", 现属版块: "+form.getLongBoardId()+", 移动至版块: "+form.getLongTargetBoardId());
		if(form.getLongTargetBoardId()>0 && form.getLongTargetBoardId() != form.getLongBoardId()){
			boolean symbol = topicService.move(
				form.getLongTopicId(), 
				form.getLongBoardId(), 
				form.getLongTargetBoardId(), 
				aec).orElse(false); 
			if (symbol) {
				return "redirect:/board/home"; 
			}
		}
		model.addAttribute("form", form);
		//
		model.addAttribute("errors", "话题移动失败");
		return "default/topic/move";
	}
	
	// 举报话题
	@PostMapping(path = "/report", produces = "application/json;charset=UTF-8")
	@ResponseBody
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_REPORT, isAjax=true)
	@Strategy(action=ForumActionEnum.TOPIC_REPORT, param=StrategyEntityParam.QUERY_STR, paramId="id")
	public TipMessage reportTopicAction(
			@RequestParam("type")int reportTypeSymbol, 
			@RequestParam("reason")String reportContent, 
			@RequestParam(value = "token", required = false, defaultValue = "0")String token, 
			@RequestParam("id")long id, 
			MemberSessionBean mbean, 
			HttpServletRequest request,
			Model model) {
		Optional<Posts> oneFloor = postsService.getTopicContent(id);
		if(!oneFloor.isPresent()) {
			return TipMessage.ofError("回复不存在或暂时无法访问");
		}
		Topic topic = oneFloor.get().getTopic();
		if(null == topic){
			return TipMessage.ofError("举报的话题不存在或暂时无法访问");
		}
		ActionEventCulpritor aec = ActionEventCulpritor.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
		//----------------------------------策略检查开始,不限制角色
		//  迁至StrategyInterceptorAdapter
		//----------------------------------策略检查结束
		Optional<BoardTopicCategoryIndex> btc = boardTopicCategoryIndexService.getReportRelativeRecord();
		if (!btc.isPresent()) {
			return TipMessage.ofError("未知的存储目标");
		}
		Optional<Topic> result = topicService.plug(new ReportPlugTopic(ForumActionEnum.TOPIC_REPORT, aec, oneFloor.get(), reportTypeSymbol, reportContent, btc.get()));
		return TipMessage.Builder.of(()->result.isPresent()).success("话题举报成功").error("操作失败");
	}
	
	// 收藏话题
	@PostMapping(path = "/favorite", produces = "application/json;charset=UTF-8")
	@ResponseBody
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_FAVORITE, isAjax=true)
	@Strategy(action=ForumActionEnum.TOPIC_FAVORITE)
	public String favoriteTopicAction(
			@RequestParam(value = "token", required = false, defaultValue = "0")String token, 
			@ModelAttribute("topicArg")Topic tpObj, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model) {
		//----------------------------------从Http Referer中获取需要的参数
		if(null==tpObj || tpObj.getId()<1){
			return TipMessage.ofError("操作参数解析失败").toJsonString();
		}
		long topicId = tpObj.getId(); 
		//----------------------------------
		Topic topic = topicService.get(topicId).orElse(Topic.empty(topicId));
		ActionEventCulpritor aec = ActionEventCulpritor.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
		//----------------------------------策略检查开始,不限制角色
		//  迁至StrategyInterceptorAdapter
		//----------------------------------策略检查结束
		try{
			if (topicService.favorite(topicId, aec).orElse(false)) {
				//缓存需要的@20200330
				Map<String,String> extOutputCacheParames = new HashMap<>();
				extOutputCacheParames.put("cacheSequence", topic.getConnect());
				extOutputCacheParames.put("cacheModule", "topic");
				extOutputCacheParames.put("cacheAction", "favorite");
				return TipMessage.ofSuccess("话题收藏成功").toJsonString(extOutputCacheParames);
			}
		}catch(IllegalStateException e){
			return TipMessage.ofError(e.getMessage()).toJsonString();
		}
		return TipMessage.ofError("操作失败或已经收藏").toJsonString();
	}
	// 是否可以进行话题收藏
	@GetMapping(path = "/favorite/check", produces = "application/json;charset=UTF-8")
	@ResponseBody
	public String initFavoriteTopicStatus(
			@ModelAttribute("topicArg")Topic tpObj, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model){
		//-------------------------------------------------从Http Referer中获取需要的参数
		if(null==tpObj || tpObj.getId()<1){
			return "{}";
		}
		Map<String,String> statsData = new HashMap<>();
		if(mbean.isOnline()){
			boolean continueAction = topicService.isFavorited(tpObj.getId(), mbean.getMid());
			statsData.put("favorited", continueAction?"1":"0"); //是否可以收藏
			statsData.put("victim", mbean.getMid()+""); 
			statsData.put("action", continueAction?"true":"false");
			//缓存需要的@20200330
			statsData.put("cacheSequence", tpObj.getConnect());
			statsData.put("cacheModule", "topic");
			statsData.put("cacheAction", "favorite");
		}else{ //匿名不可以
			statsData.put("favorited", "0"); 
			statsData.put("victim", "0"); 
			statsData.put("action", "true"); //按钮是否可用
		}
		return Commons.toJson(statsData);
	}
	//取消收藏
	@PostMapping(path = "/favorite/cancel", produces = "application/json;charset=UTF-8")
	@ResponseBody
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_FAVORITE_CANCEL, isAjax=true)
	@Strategy(action=ForumActionEnum.TOPIC_FAVORITE_CANCEL, param=StrategyEntityParam.QUERY_STR, paramId="id")
	public String undoFavoriteTopicAction(
			@RequestParam(value = "token", required = false, defaultValue = "0")String token, 
			@RequestParam("id")long id, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model){
		if(id<1 || !mbean.isOnline()){
			return TipMessage.ofError("参数丢失或不合法").toJsonString();
		}
		//----------------------------------
		Topic topic = topicService.get(id).orElse(Topic.empty(id));
		ActionEventCulpritor aec = ActionEventCulpritor.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
		//----------------------------------策略检查开始,不限制角色
		//  迁至StrategyInterceptorAdapter
		//----------------------------------策略检查结束
		try{
			if (topicService.removeFavorite(id, aec).orElse(false)) {
				//缓存需要的@20200330
				Map<String,String> extOutputCacheParames = new HashMap<>();
				extOutputCacheParames.put("cacheSequence", topic.getConnect());
				extOutputCacheParames.put("cacheModule", "topic");
				extOutputCacheParames.put("cacheAction", "favorite");
				extOutputCacheParames.put("cacheValue", "true");
				return TipMessage.ofSuccess("已成功取消收藏").toJsonString(extOutputCacheParames);
			}
		}catch(IllegalStateException e){
			return TipMessage.ofError(e.getMessage()).toJsonString();
		}
		return TipMessage.ofError("操作失败或已取消收藏").toJsonString();
	}
	// 话题点赞
	@PostMapping(path = "/like", produces = "application/json;charset=UTF-8")
	@ResponseBody
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_LIKED, isAjax=true)
	@Strategy(action=ForumActionEnum.TOPIC_LIKED)
	public String likedTopicAction(
			@RequestParam(value = "token", required = false, defaultValue = "0")String token, 
			@ModelAttribute("topicArg")Topic tpObj, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model) {
		//----------------------------------从Http Referer中获取需要的参数
		if(null==tpObj || tpObj.getId()<1){
			return TipMessage.ofError("操作参数解析失败").toJsonString();
		}
		long topicId = tpObj.getId(); 
		//----------------------------------
		Topic topic = topicService.get(topicId).orElse(Topic.empty(topicId));
		ActionEventCulpritor aec = ActionEventCulpritor.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
		//----------------------------------策略检查开始,不限制角色
		//  迁至StrategyInterceptorAdapter
		//----------------------------------策略检查结束
		try{
			if (topicService.like(topicId, aec).orElse(false)) {
				//缓存需要的@20200330
				Map<String,String> extOutputCacheParames = new HashMap<>();
				extOutputCacheParames.put("cacheSequence", topic.getConnect());
				extOutputCacheParames.put("cacheModule", "topic");
				extOutputCacheParames.put("cacheAction", "like");
				return TipMessage.ofSuccess("话题点赞成功").toJsonString(extOutputCacheParames);
			}
		}catch(IllegalStateException e){
			return TipMessage.ofError(e.getMessage()).toJsonString();
		}
		return TipMessage.ofError("点赞失败").toJsonString();
	}
	// 是否可以进行话题点赞
	@GetMapping(path = "/like/check", produces = "application/json;charset=UTF-8")
	@ResponseBody
	public String initLikeTopicStatus(
			@ModelAttribute("topicArg")Topic tpObj, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model){
		//-------------------------------------------------从Http Referer中获取需要的参数
		if(null==tpObj || tpObj.getId()<1){
			return "{}";
		}
		Map<String,String> statsData = new HashMap<>();
		if(mbean.isOnline()){
			boolean continueAction = topicService.isLiked(tpObj.getId(), mbean.getMid());
			statsData.put("liked", continueAction?"1":"0"); //是否可以点赞
			statsData.put("victim", mbean.getMid()+"");
			statsData.put("action", continueAction?"true":"false");
			//缓存需要的@20200330
			statsData.put("cacheSequence", tpObj.getConnect());
			statsData.put("cacheModule", "topic");
			statsData.put("cacheAction", "like");
		}else{ //匿名不可以
			statsData.put("liked", "0"); 
			statsData.put("victim", "0"); 
			statsData.put("action", "true"); //按钮是否可用
		}
		return new Gson().toJson(statsData);
	}
	//取消点赞
	@PostMapping(path = "/like/cancel", produces = "application/json;charset=UTF-8")
	@ResponseBody
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_LIKED_CANCEL, isAjax=true)
	@Strategy(action=ForumActionEnum.TOPIC_LIKED_CANCEL, param=StrategyEntityParam.QUERY_STR, paramId="id")
	public String undoLikedTopicAction(
			@RequestParam(value = "token", required = false, defaultValue = "0")String token, 
			@RequestParam("id")long id, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model mode){
		if(id<1 || !mbean.isOnline()){
			return TipMessage.ofError("参数丢失或不合法").toJsonString();
		}
		//----------------------------------
		Topic topic = topicService.get(id).orElse(Topic.empty(id));
		ActionEventCulpritor aec = ActionEventCulpritor.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
		//----------------------------------策略检查开始,不限制角色
		//  迁至StrategyInterceptorAdapter
		//----------------------------------策略检查结束
		try{
			if (topicService.removeLike(id, aec).orElse(false)) {
				//缓存需要的@20200330
				Map<String,String> extOutputCacheParames = new HashMap<>();
				extOutputCacheParames.put("cacheSequence", topic.getConnect());
				extOutputCacheParames.put("cacheModule", "topic");
				extOutputCacheParames.put("cacheAction", "like");
				extOutputCacheParames.put("cacheValue", "true");
				return TipMessage.ofSuccess("赞已成功取消").toJsonString(extOutputCacheParames);
			}
		}catch(IllegalStateException e){
			return TipMessage.ofError(e.getMessage()).toJsonString();
		}
		return TipMessage.ofError("赞取消失败").toJsonString();
	}
	// 浏览话题
	@GetMapping(path = "/browse", produces = "application/json;charset=UTF-8")
	@ResponseBody
	public TipMessage browseTopicAction( 
			@RequestParam(value = "token", required = false, defaultValue = "0")String token, 
			@ModelAttribute("topicArg")Topic tpObj, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model) {
		//-------------------------------------------------从Http Referer中获取需要的参数
		if(null==tpObj || tpObj.getId()<1){
			return TipMessage.ofError("操作参数解析失败");
		}
		long topicId = tpObj.getId(); 
		//-------------------------------------------------
		ActionEventCulpritor aec = ActionEventCulpritor.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
		return TipMessage.Builder.take(()->topicService.browse(topicId, aec)).success("话题浏览成功").error("操作失败");
	}
	
	// 话题配置
	@GetMapping(path="/config/edit")
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_CONFIG_EDIT)
	@Strategy(action=ForumActionEnum.TOPIC_CONFIG_EDIT, allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
	public String topicConfigForm(
			@RequestParam(value = "token", required = false, defaultValue = "0")String token, 
			@ModelAttribute("topicArg")Topic tpObj, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model) {
		//----------------------------------从Http Referer中获取需要的参数
		if(null==tpObj || tpObj.getId()<1){
			throw new ResourceNotFoundException("操作参数解析失败");
		}
		long topicId = tpObj.getId(); long boardId = tpObj.getBoardId(); int boardGroupId = tpObj.getVolumesId();
		//----------------------------------
		Topic topic = topicService.get(topicId).orElse(Topic.empty(topicId));
		//----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以编辑
		//  迁至StrategyInterceptorAdapter
		//----------------------------------策略检查结束
		TopicConfig c = topicConfigService.getByTopicId(topicId).orElse(TopicConfig.defaultConfig(topicId));
		TopicConfigForm form = new TopicConfigForm();
		//
		form.setPrivacy(c.isPrivacy());
		form.setReply(c.isReply());
		form.setNotify(c.isNotify());
		form.setAtomPoster(c.isAtomPoster());
		form.setWriteMinInterrupt(c.getWriteMinInterrupt()+"");
		//r
		form.setReadMinScore(c.getReadMinScore()+"");
		form.setReadLowMemberGroup(c.getReadLowMemberGroup().getSymbol()+"");
		form.setReadLowMemberRole(c.getReadLowMemberRole().getSymbol()+"");
		form.setReadLowMemberLevel(c.getReadLowMemberLevel()+"");
		//w
		form.setWriteMinScore(c.getWriteMinScore()+"");
		form.setWriteLowMemberGroup(c.getWriteLowMemberGroup().getSymbol()+"");
		form.setWriteLowMemberRole(c.getWriteLowMemberRole().getSymbol()+"");
		form.setWriteLowMemberLevel(c.getWriteLowMemberLevel()+"");
		
		//
		form.setRecord(c.getId());
		form.setTopicId(c.getTopicId()+"");
		form.setBoardId(boardId+"");
		form.setVolumesId(boardGroupId+"");
		//
		form.setToken(token);
		model.addAttribute("form", form);
		Board board = boardService.get(boardId, boardGroupId);
		topic.setBoard(board);
		model.addAttribute("topic", topic);
		//
		return "default/topic/config";
	}
	@PostMapping(path="/config/edit")
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_CONFIG_EDIT)
	@Strategy(action=ForumActionEnum.TOPIC_CONFIG_EDIT, param=StrategyEntityParam.QUERY_STR, paramId="topicId", allowRoles={MemberRoleEnum.ADMIN, MemberRoleEnum.MASTER, MemberRoleEnum.BM})
	public String topicConfigAction(@ModelAttribute("form")TopicConfigForm form, MemberSessionBean mbean, HttpServletRequest request, Model model) {
		Topic topic = topicService.get(form.getLongTopicId()).orElse(Topic.empty(form.getLongTopicId()));
		ActionEventCulpritor aec = ActionEventCulpritor.getInstance(mbean.getMid(), mbean.getNickname(), request, form.getToken());
		//----------------------------------策略检查开始,限制角色:管理员,版主,大版主都可以编辑
		//  迁至StrategyInterceptorAdapter
		//----------------------------------策略检查结束
		TopicConfig config = new TopicConfig();
		config.setId(form.getLongRecord());
		config.setTopicId(form.getLongTopicId());
		config.setPrivacy(form.getBooleanPrivacy());
		config.setReply(form.getBooleanReply());
		config.setNotify(form.getBooleanNotify());
		config.setAtomPoster(form.getBooleanAtomPoster());
		config.setWriteMinInterrupt(form.getIntegerWriteMinInterrupt());
		//r
		config.setReadMinScore(form.getIntegerReadMinScore());
		MemberGroupEnum rmg = EnumArchitecture.getInstance(form.getIntegerReadLowMemberGroup(), MemberGroupEnum.class).orElse(MemberGroupEnum.GUEST);
		config.setReadLowMemberGroup(rmg);
		MemberRoleEnum rmr = EnumArchitecture.getInstance(form.getIntegerReadLowMemberRole(), MemberRoleEnum.class).orElse(MemberRoleEnum.NO);
		config.setReadLowMemberRole(rmr);
		config.setReadLowMemberLevel(form.getIntegerReadLowMemberLevel());
		//w
		config.setWriteMinScore(form.getIntegerWriteMinScore());
		MemberGroupEnum wmg = EnumArchitecture.getInstance(form.getIntegerWriteLowMemberGroup(), MemberGroupEnum.class).orElse(MemberGroupEnum.CARD);
		config.setWriteLowMemberGroup(wmg);
		MemberRoleEnum wmr = EnumArchitecture.getInstance(form.getIntegerWriteLowMemberRole(), MemberRoleEnum.class).orElse(MemberRoleEnum.NO);
		config.setWriteLowMemberRole(wmr);
		config.setWriteLowMemberLevel(form.getIntegerWriteLowMemberLevel());
		
		String errMsg=null;
		try{
			boolean symbol = topicService.editTopicConfig(
				config.getTopicId(), 
				config, 
				config.getId(), 
				aec).orElse(false);
			if(symbol) {
				return String.format("redirect:/topic/%s.xhtml", ActiveDirectoryConnectorFactory.generateConnectString(form.getIntegerVolumesId(), form.getLongBoardId(), config.getTopicId()));
			}else{
				errMsg="更新话题的回复策略配置失败";
			}
		}catch(IllegalStateException e){
			errMsg=e.getMessage();
		}
		model.addAttribute("form", form);
		Board board = boardService.get(form.getLongBoardId(), form.getIntegerVolumesId());
		topic.setBoard(board);
		model.addAttribute("topic", topic);
		//
		model.addAttribute("errors", errMsg);
		//
		return "default/topic/config";
	}
	
	// 工具菜单
	@GetMapping(path = "/tool.jsonp", produces = "application/javascript;charset=UTF-8")
	@ResponseBody
	public String getTopicToolMenus(
			@RequestParam("callback") String callBackFun, 
			@RequestParam(value = "box", required = false, defaultValue = "null") String boxEle, 
			@ModelAttribute("topicArg")Topic tpObj, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model){
		//-------------------------------------------------从Http Referer中获取需要的参数
		if(null==tpObj || tpObj.getId()<1){
			return callBackFun + "({});";
		}
		long topicId = tpObj.getId(); long boardId = tpObj.getBoardId(); int boardGroupId = tpObj.getVolumesId();
		//-------------------------------------------------
		Map<String, List<CommonLink>> menues = new HashMap<>();
		List<CommonLink> publicLink = Arrays.asList(
				CommonLinkBuilder.setAnchor("收藏").setLink("/topic/favorite").isAjax(true).build(), 
				CommonLinkBuilder.setAnchor("订阅").setLink("/rss/topic.xml?id="+topicId).build(), 
				CommonLinkBuilder.setAnchor("文本").build(), 
				CommonLinkBuilder.setAnchor("打印").build());
		menues.put("public", publicLink);
		if(MemberRoleEnum.ADMIN == mbean.getRole() || boardModeratorService.get(boardGroupId, boardId, mbean.getMid()).isPresent()){
			List<CommonLink> manageLink = Arrays.asList(
					CommonLinkBuilder.setAnchor("删除").setLink("/topic/delete").isAjax(true).build(), 
					CommonLinkBuilder.setAnchor("锁定").setLink("/topic/lock").isAjax(true).build(),
					CommonLinkBuilder.setAnchor("精华").setLink("/topic/best").isAjax(true).build(), 
					CommonLinkBuilder.setAnchor("置顶").setLink("/topic/top").isAjax(true).build(), 
					CommonLinkBuilder.setAnchor("移动").setLink("/topic/move").build(), 
					CommonLinkBuilder.setAnchor("配置").setLink("/topic/config/edit").build());
			menues.put("manager", manageLink);
		}
		Map<String, Object> result = new HashMap<>();
		result.put("element", boxEle);
		result.put("result", menues);
		Gson gson = new Gson();
		return callBackFun + "(" + gson.toJson(result) + ");";
	}
	// ------------------------------------------------------------------------------以上是话题的操作
	
	// 发布|创建话题
	@GetMapping(path="/create")
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_PUBLISH)
	@Strategy(action=ForumActionEnum.TOPIC_PUBLISH, mode=StrategyMode.WRITE, handler=BoardDetectionStrategy.class)
	public String createTopicForm(
			@RequestParam(value = "token", required = false, defaultValue = "0")String token, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model)  {
		long boardId = 0L; int boardGroupId = -1;
		//-----------------------------------从Referer中获取boardId&boardGroupId
		Optional<Board> adObj = ActiveDirectoryConnectorFactory.parseRefererToBoard(request.getHeader("referer"), siteDomain);
		if(adObj.isPresent()){
			boardId = adObj.get().getId();
			boardGroupId = adObj.get().getVolumesId();
		}
		//再尝试一下
		if(boardId == 0 && boardGroupId == -1){
			Optional<Topic> tpObj = ActiveDirectoryConnectorFactory.parseRefererToTopic(request.getHeader("referer"), siteDomain);
			if(tpObj.isPresent()){
				boardId = tpObj.get().getBoardId();
				boardGroupId = tpObj.get().getVolumesId();
			}
		}
		if(boardId == 0 && boardGroupId == -1){
			throw new ResourceNotFoundException("发布话题需要的参数解析失败");
		}
		//-----------------------------------对创建话题进行策略检查
		//  迁至StrategyInterceptorAdapter
		//----------------------------------
		TopicForm form = new TopicForm();
		form.setCategory("0");
		form.setBoard(boardId+"");
		form.setVolumes(boardGroupId+"");
		form.setToken(token); 
		model.addAttribute("form", form);
		return "default/topic/create";
	}
	@PostMapping(path="/create")
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_PUBLISH)
	@Strategy(action=ForumActionEnum.TOPIC_PUBLISH, param=StrategyEntityParam.QUERY_STR, paramId="board", mode=StrategyMode.WRITE, handler=BoardDetectionStrategy.class)
	public String createTopicAction(
			@ModelAttribute("form")TopicForm form, 
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			Model model) {
		if(!Commons.isNotBlank(form.getTitle())){
			throw new ForumValidateException("话题标题不可以为空");
		}
		if(!Commons.isNotBlank(form.getContent())){
			throw new ForumValidateException("话题内容不可以为空");
		}
		long boardId = form.getLongBoard();
		int volumesId = form.getIntegerVolumes();
		ActionEventCulpritor aec = ActionEventCulpritor.getInstance(mbean.getMid(), mbean.getNickname(), request, form.getToken());
		//----------------------------------对创建话题进行策略检查
		//  迁至StrategyInterceptorAdapter
		//----------------------------------
		if(boardId>0 && volumesId>=0) { //有可能在默认分组中
			long data = topicService.create(
					volumesId, 
					boardId, 
					form.getIntegerCategory(), 
					form.getTitle(), 
					form.getContent(), 
					imageIOConfig, 
					aec);
			if(data>0) {
				return String.format("redirect:/topic/%s.xhtml", ActiveDirectoryConnectorFactory.generateConnectString(volumesId, boardId, data));
			}
		}
		model.addAttribute("form", form);
		//
		model.addAttribute("errors", "发布话题操作失败");
		return "default/topic/create";
	}
	
	//异步加载指定话题下的回复(不包含1楼的[贴子的内容]回复)
	@GetMapping(path = "/replier.json", produces = "application/json;charset=UTF-8")
	@ResponseBody
	public String getTopicPosts(
			@RequestParam("author") long oneFloorMemberId, 
			@RequestParam("pageNumber")int page, 
			@RequestParam("pageSize")int pageSize, 
			@RequestParam(value = "filter", required = false, defaultValue = "0") long filterMemberId, 
			@RequestParam(value = "scale", required = false, defaultValue = "auto") String imageScale, 
			@ModelAttribute("topicArg")Topic tpObj, 
			HttpServletRequest request, 
			Model model) {
		//-------------------------------------------------从Http Referer中获取需要的参数
		if(null==tpObj || tpObj.getId()<1){
			return "{}";
		}
		//-------------------------------------------------
		String dictionaryFilePath = request.getServletContext().getRealPath("/WEB-INF/"+sensitiveDictionary);
		Page<Posts> rs = postsService.getAllReplyFilterAuthor(tpObj.getId(), filterMemberId, new PageRequest(page, pageSize), imageIOConfig, true, imageScale, dictionaryFilePath);
		List<ForumReplierDigest> data = rs.getResult().map(posts -> new ForumReplierDigest(posts, oneFloorMemberId)).collect(Collectors.toList());
		if(rs.getTotalElements() == 0){
			return "{}";
		}
		Map<String,Object> result = new HashMap<>();
		result.put("result", data);
		result.put("total", rs.getTotalElements());
		result.put("topic", tpObj.getId());
		if(filterMemberId>=1){
			result.put("filter", filterMemberId);
		}
		result.put("page", page);
		result.put("size", pageSize);
		Gson gson = new Gson();
		return gson.toJson(result);
	}
	
	//话题的海报,用以分享
	@GetMapping(path = "/poster")
	public String getTopicPoster(
			@ModelAttribute("topicArg")Topic tpObj, 
			@RequestParam(value = "topic", required = false, defaultValue = "0") long topicId, 
			@RequestParam(value = "scale", required = false, defaultValue = "640x400")String scale, 
			HttpServletRequest request,
			Model model){
		long tmpTopicId = topicId;
		if(tmpTopicId == 0 && null != tpObj){
			tmpTopicId = tpObj.getId();
		}
		Topic topicObj = topicService.get(tmpTopicId).orElseThrow(()->new ResourceNotFoundException("操作参数解析失败"));
		model.addAttribute("topic", topicObj);
		//
		Album album = albumService.getByTopic(topicObj.getId()).orElse(new Album());
		String imagePath = new ImagePathCoverter(album.getCoverLink())
								.decodeUploadImageFilePath(imageIOConfig.getImageBucketDomain(), imageIOConfig.getUploadImageDirectName(), scale)
								.orElse("/static/img/default.poster.bg.jpg"); //使用默认的封面图
		model.addAttribute("cover", imagePath);
		//
		MemberProfileBean guestMemberProfile = MemberProfileBean.guest();
		EnumMap<ForumActionEnum, Long> rawdata = topicActionCollectionService.groupMemberTopicAction(topicObj.getMemberId());
		MemberProfileBean mpb = memberService.getMemberProfileBean(topicObj.getMemberId(), rawdata).orElse(guestMemberProfile);
		model.addAttribute("profile", mpb);
		return "default/topic/poster";
	}
	
	//话题的二维码
	@GetMapping(path = "/qrcode/{id}.png", produces = MediaType.IMAGE_PNG_VALUE)
	public ResponseEntity<byte[]> getTopicQRCode(
			@PathVariable("id") long id, 
			HttpServletRequest request, 
			HttpServletResponse response, 
			Model model) {
		Topic topic = topicService.get(id).orElse(Topic.empty(id));
		if(!Commons.isNotBlank(topic.getTitle())){
			throw new ResourceNotFoundException("操作参数解析失败");
		}
		//是否存在缓存
		TopicFileCache tfc = TopicFileCache.getInstance(servletContext.getRealPath("/"));
		File qrcodeCacheFile = tfc.getFile("qrcode.png", 
										topic.getConnect(), 
										TopicQRCodeFileCacheHandler.from(String.format("%s/topic/%s.xhtml", siteDomain, topic.getConnect()))
																	.scale(128, 128)
																	.format(ImageType.PNG)
																	.handler());
		if(null == qrcodeCacheFile){
			throw new ResourceNotFoundException("二维码文件计算失败");
		}
		//
		try{
			byte[] qrcodeBytes = FileUtils.readFileToByteArray(qrcodeCacheFile);
			final HttpHeaders headers = new HttpHeaders();
			headers.setContentType(MediaType.IMAGE_PNG);
			headers.setContentLength(qrcodeBytes.length);
			return new ResponseEntity<byte[]>(qrcodeBytes, headers, HttpStatus.CREATED);
		}catch (IOException e) {
			logger.info("[QRCode] has exception: "+e.getMessage());
			response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
			return null;
		}
	}
	
	@GetMapping("/poster/down")
	@OnlineDescriptor(action=ForumActionEnum.TOPIC_SHARE, isAjax=false)
	public ResponseEntity<byte[]> downloadFile(
			@RequestParam(value = "token", required = true)String token, 
			@RequestParam("topic") long id, 
			@RequestHeader(value = "referer", required = true) final String referer, 
			@RequestParam(value = "stamp", required = false, defaultValue = "0") long timestamp,
			MemberSessionBean mbean, 
			HttpServletRequest request, 
			HttpServletResponse response, 
			Model model) throws IOException {
		if(null == referer || timestamp <= 0 || !referer.contains("/topic/poster")){
			throw new ResourceNotFoundException("操作参数解析失败");
		}
		//是否存在缓存
		TopicFileCache tfc = TopicFileCache.getInstance(servletContext.getRealPath("/"));
		String postURI = String.format("%s/topic/poster?topic=%d", siteDomain, id);
		ActionEventCulpritor aec = ActionEventCulpritor.getInstance(mbean.getMid(), mbean.getNickname(), request, token);
		File posterCacheFile = topicService.getPoster(id, postURI, tfc, aec).orElseThrow(()->new ResourceNotFoundException("海报文件计算失败"));
		//
		byte[] outputByteArray = FileUtils.readFileToByteArray(posterCacheFile);
		HttpHeaders headers = new HttpHeaders();
		headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); 
		headers.setContentDispositionFormData("attachment", "poster.png");
		//https://github.com/johnculviner/[jquery.fileDownload]需要的
		//http://johnculviner.com/jquery-file-download-plugin-for-ajax-like-feature-rich-file-downloads/
		headers.set(HttpHeaders.SET_COOKIE, "fileDownload=true; path=/");
		return new ResponseEntity<byte[]>(outputByteArray, headers, HttpStatus.OK);
	}
	
	//话题相关性
	@GetMapping(path = "/relate.json", produces = "application/json;charset=UTF-8")
	@ResponseBody
	public String getRelateTopices(
			@ModelAttribute("topicArg")Topic tpObj, 
			@RequestParam(value = "size", required = false, defaultValue = "10") int showSize, 
			HttpServletRequest request,
			Model model){
		// -------------------------------------------------从Http Referer中获取需要的参数
		if (null == tpObj || tpObj.getId() < 1) {
			return "{}";
		}
		// -------------------------------------------------
		Function<TagRelateTopic, Map<String, String>> mapper = trt -> {
			Map<String, String> extInfoMap = new HashMap<>();
			extInfoMap.put("ranking", trt.getRanking() + "");
			extInfoMap.put("analogy", new DecimalFormat("#.00").format(trt.getSimilarity()));
			return new ForumThreadsDigest(trt.getTopic()).toMergeMap(extInfoMap);
		};
		List<Map<String, String>> data = topicService.getRelateTopic(tpObj.getId(), showSize).sorted().map(mapper).collect(Collectors.toList());
		if (data.isEmpty()) {
			return "{}";
		}
		return new Gson().toJson(data);
	}
	
	//异步加载话题的统计
	@GetMapping(path = "/stats.json", produces = "application/json;charset=UTF-8")
	@ResponseBody
	public String getTopicStats(
			@ModelAttribute("topicArg")Topic tpObj, 
			MemberSessionBean mbean,  
			HttpServletRequest request,
			Model model){
		//-------------------------------------------------从Http Referer中获取需要的参数
		if(null==tpObj || tpObj.getId()<1){
			return "{}";
		}
		//-------------------------------------------------
		TopicStats ts = topicStatsService.getByTopic(tpObj.getId()).orElse(new TopicStats(tpObj.getId(), tpObj.getVolumesId(), tpObj.getBoardId()));
		Map<String,Long> statsData = ts.statsMap();
		if(mbean.isOnline()){
			statsData.put("liked", topicService.isLiked(tpObj.getId(), mbean.getMid())?1L:0L); //是否可以点赞
			statsData.put("favorited", topicService.isFavorited(tpObj.getId(), mbean.getMid())?1L:0L); //是否可以收藏
			statsData.put("victim", mbean.getMid()); 
		}else{ //匿名不可以
			statsData.put("liked", 0L); 
			statsData.put("favorited", 0L); 
			statsData.put("victim", 0L); 
		}
		return new Gson().toJson(statsData);
	}
	//话题内容的编辑
	//ETC
}